package net.oschina.esb.webmvc.exception;

import net.oschina.esb.exception.CodeException;
import net.oschina.esb.webmvc.exception.handler.ExceptionViewResolver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.core.Ordered;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * @author Arvin
 * @time 2017/12/12 22:01
 */
public class ExtendErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

    private static final Log logger = LogFactory.getLog(ExtendErrorAttributes.class);

    private static final String ERROR_ATTRIBUTE = AutoWebmvcExceptionConfiguration.class.getName()
            + ".ERROR";

    private List<ExceptionViewResolver> exceptionViewResolverList;

    public ExtendErrorAttributes(List<ExceptionViewResolver> exceptionViewResolverList) {
        // 排序
        this.exceptionViewResolverList = sortExceptionViewResolverList(exceptionViewResolverList);

        logger.info("创建异常Attribute: " + this.getClass().getName());
    }

    /**
     * 排序
     *
     * @param exceptionViewResolverList 要排序的列表
     */
    private List<ExceptionViewResolver> sortExceptionViewResolverList(List<ExceptionViewResolver> exceptionViewResolverList) {

        Collections.sort(exceptionViewResolverList, new Comparator<ExceptionViewResolver>() {
            @Override
            public int compare(ExceptionViewResolver o1, ExceptionViewResolver o2) {
                int ret = o1.getOrder() - o2.getOrder();
                return ret == 0 ? 0 : ret > 0 ? 1 : -1;
            }
        });

        return exceptionViewResolverList;
    }

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {

        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        Throwable throwable = getError(requestAttributes);

        int code = 500;
        String message = "未知错误";
        if (null != throwable) {
            if (throwable instanceof CodeException) {
                CodeException exception = (CodeException) throwable;
                code = exception.getCode();
            }
            message = throwable.getMessage();
        }

        errorAttributes.put("code", code);
        errorAttributes.put("message", message);

        return errorAttributes;
    }

    @Override
    public Throwable getError(RequestAttributes requestAttributes) {
        Throwable exception = getAttribute(requestAttributes, ERROR_ATTRIBUTE);
        if (exception == null) {
            exception = getAttribute(requestAttributes, "javax.servlet.error.exception");
        }
        return exception;
    }

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        storeErrorAttributes(request, ex);

        if (null != ex && handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            for (ExceptionViewResolver exceptionViewResolver : this.exceptionViewResolverList) {
                if (exceptionViewResolver.canHandle(handlerMethod)) {
                    ModelAndView mav = exceptionViewResolver.resolveException(request, response, handlerMethod, ex);
                    if (null != mav) {
                        return mav;
                    }
                }
            }
        }
        return null;
    }

    private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
        request.setAttribute(ERROR_ATTRIBUTE, ex);
    }

    @SuppressWarnings("unchecked")
    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
        return (T) requestAttributes.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
